iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Modern Web

React 學得動嗎系列 第 21

[Day 21] Gym Pro:實作會員管理功能

  • 分享至 

  • xImage
  •  

今天,我們將實作健身房管理系統中一個非常關鍵的功能——會員管理。我們會構建會員列表頁面和會員詳情頁面,並且學習如何使用 TanStack TableReact Hook Form 這些功能強大的工具來實現管理功能。這些功能將幫助我們有效地管理健身房的會員資料。

1. 會員列表頁面

首先,我們會建立一個會員列表頁面,使用 TanStack Table 來處理表格的排序和分頁功能,這樣可以更方便地管理大量的會員資料。

src/pages/Members.tsx 中:

import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTable, useSortBy, usePagination } from '@tanstack/react-table';
import axios from 'axios';
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"

interface Member {
  id: number;
  name: string;
  email: string;
  joinDate: string;
  membershipType: string;
}

const fetchMembers = async (): Promise<Member[]> => {
  const { data } = await axios.get<Member[]>('/api/members');
  return data;
};

const Members: React.FC = () => {
  const { data: members, isLoading, error } = useQuery(['members'], fetchMembers);

  const columns = React.useMemo(
    () => [
      {
        Header: '姓名',
        accessor: 'name',
      },
      {
        Header: 'Email',
        accessor: 'email',
      },
      {
        Header: '加入日期',
        accessor: 'joinDate',
      },
      {
        Header: '會員類型',
        accessor: 'membershipType',
      },
    ],
    []
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    page,
    nextPage,
    previousPage,
    canNextPage,
    canPreviousPage,
    prepareRow,
  } = useTable(
    { columns, data: members || [] },
    useSortBy,
    usePagination
  );

  if (isLoading) return <div>載入中...</div>;
  if (error) return <div>發生錯誤:{(error as Error).message}</div>;

  return (
    <div>
      <h1 className="text-2xl font-bold mb-4">會員列表</h1>
      <Table {...getTableProps()}>
        <TableHeader>
          {headerGroups.map(headerGroup => (
            <TableRow {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <TableHead {...column.getHeaderProps(column.getSortByToggleProps())}>
                  {column.render('Header')}
                  <span>
                    {column.isSorted
                      ? column.isSortedDesc
                        ? ' 🔽'
                        : ' 🔼'
                      : ''}
                  </span>
                </TableHead>
              ))}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody {...getTableBodyProps()}>
          {page.map(row => {
            prepareRow(row);
            return (
              <TableRow {...row.getRowProps()}>
                {row.cells.map(cell => (
                  <TableCell {...cell.getCellProps()}>{cell.render('Cell')}</TableCell>
                ))}
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
      <div className="mt-4">
        <Button onClick={() => previousPage()} disabled={!canPreviousPage}>
          上一頁
        </Button>
        <Button onClick={() => nextPage()} disabled={!canNextPage}>
          下一頁
        </Button>
      </div>
    </div>
  );
};

export default Members;

2. 會員詳情頁面

接下來,我們會實作會員詳情頁面,這個頁面允許管理員查看並編輯特定會員的詳細資料。我們會使用 React Hook Form 來處理表單提交和表單驗證。

src/pages/MemberDetail.tsx 中:

import React from 'react';
import { useParams } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import axios from 'axios';
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/components/ui/card"

interface Member {
  id: number;
  name: string;
  email: string;
  joinDate: string;
  membershipType: string;
}

const fetchMember = async (id: string): Promise<Member> => {
  const { data } = await axios.get<Member>(`/api/members/${id}`);
  return data;
};

const updateMember = async (member: Member): Promise<Member> => {
  const { data } = await axios.put<Member>(`/api/members/${member.id}`, member);
  return data;
};

const MemberDetail: React.FC = () => {
  const { id } = useParams<{ id: string }>();
  const queryClient = useQueryClient();
  const { data: member, isLoading, error } = useQuery(['member', id], () => fetchMember(id!));

  const { register, handleSubmit, formState: { errors } } = useForm<Member>();

  const mutation = useMutation(updateMember, {
    onSuccess: () => {
      queryClient.invalidateQueries(['member', id]);
    },
  });

  const onSubmit = (data: Member) => {
    mutation.mutate(data);
  };

  if (isLoading) return <div>載入中...</div>;
  if (error) return <div>發生錯誤:{(error as Error).message}</div>;

  return (
    <Card>
      <CardHeader>
        <CardTitle>會員詳情</CardTitle>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit(onSubmit)}>
          <div className="space-y-4">
            <Input {...register('name', { required: '請輸入姓名' })} defaultValue={member?.name} placeholder="姓名" />
            {errors.name && <p className="text-red-500">{errors.name.message}</p>}

            <Input {...register('email', { required: '請輸入 Email' })} defaultValue={member?.email} placeholder="Email" />
            {errors.email && <p className="text-red-500">{errors.email.message}</p>}

            <Input {...register('joinDate')} defaultValue={member?.joinDate} placeholder="加入日期" />
            <Input {...register('membershipType')} defaultValue={member?.membershipType} placeholder="會員類型" />
          </div>
          <Button type="submit" className="mt-4">更新會員資料</Button>
        </form>
      </CardContent>
    </Card>
  );
};

export default MemberDetail;

3. 更新路由設定

最後,我們需要將這些新頁面整合到我們的路由中。在 src/App.tsx 中:

import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import MainLayout from './layouts/MainLayout';
import Dashboard from './pages/Dashboard';
import Members from './pages/Members';
import MemberDetail from './pages/MemberDetail';
import Login from './pages/Login';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/" element={<MainLayout />}>
          <Route index element={<Dashboard />} />
          <Route path="members" element={<Members />} />
          <Route path="members/:id" element={<MemberDetail />} />
        </Route>
      </Routes>
    </Router>
  );
}

export default App;

小結

今天我們完成了 Gym Pro 系統的會員管理功能,包括以下內容:

  • 使用 TanStack Table 實作了一個會員列表頁面,具備排序和分頁功能。
  • 建立了會員詳情頁面,使用 React Hook Form 來處理表單輸入、驗證及資料提交。
  • 使用 React Query 的 useMutation 來處理會員資料的更新,確保會員資訊可以即時更新並反映在頁面上。

上一篇
[Day 20] Gym Pro:設計登入頁面和主導航,整合 React Query
下一篇
[Day 22] Gym Pro:課程管理系統的實作與日曆視圖整合
系列文
React 學得動嗎30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言